package org.orbis.memoryblock.content;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import org.msgpack.MessagePackObject;
import org.msgpack.object.ArrayType;
import org.msgpack.object.IntegerType;
import org.msgpack.object.RawType;
import org.orbis.constants.Constants;
import org.orbis.memoryblock.MemoryBlock;
import org.orbis.structure.LookupPointer;
import org.orbis.toolbox.ByteConverter;

/** Implements skip pointer for content.<br>
 * Data structure as follows:
 * <dl>
 * <dt>[Block]</dt>
 * <dd> --&gt; &lt;[Next pointer] [ID] &lt;[Appendix pointer]&gt;* [String length] [String]&gt;</dd>
 * <dt>[Next pointer]</dt>
 * <dd> --&gt; (Pair of 3-byte digits, pointer to incremental ID)</dd>
 * <dt>[ID]</dt>
 * <dd> --&gt; (8-byte digits)</dd>
 * <dt>[Appendix pointer]</dt>
 * <dd> --&gt; (Pair of 3-byte digits, pointer to appendix fields)</dd>
 * <dt>[String length]</dt>
 * <dd> --&gt; (2-byte digits, length of [String] in bytes)</dd>
 * <dt>[String]</dt>
 * <dd> --&gt; (Byte array)</dd>
 * </dl>
 * 
 * @author iamyusk */
public class ContentBlock extends MemoryBlock{
	public static final int OPT_LEN = 0;
	public static final int OPT_APP = 1;
	public static final int OPT_TYPE = 2;
	public static final int OPT_REF = 3;
	public static final int OPT_NEXT = 4;
	public static final int OPTSIZE = 5;

	// With outer block
	public ContentBlock(ByteBuffer body, int bodySize, int ID, int cluster){
		super(body, bodySize, ID, Constants.MEMORY_TYPE_CONTENTS, cluster);
	}

	public ContentBlock(ByteBuffer existingBody){
		super(existingBody);
	}

	public ContentPackage readAll(int ptr){
		ContentPackage pkg = ContentPackage.setPackage(option[OPT_APP], getID(), ptr).cluster(getCluster());

		pkg.next(getPointer(ptr, PTR));
		ptr += PTR;
		pkg.ID(getLong(ptr, LONG));
		ptr += LONG;
		if(option[OPT_TYPE] == 0){
			for(int i = 0; i < option[OPT_APP]; i++){
				pkg.appendix(getPointer(ptr, PTR), i);
				ptr += PTR;
			}
		}
		int strlen = getInt(ptr, STRLEN);
		ptr += STRLEN;
		pkg.contentraw(read(ptr, strlen));

		return pkg;
	}

	public LookupPointer readNext(int ptr){
		LookupPointer nextPointer = getPointer(ptr, PTR);

		return nextPointer;
	}

	public LookupPointer[] readAppendixPointers(int ptr, int cluster){
		if(option[OPT_TYPE] != 0) return null;
		LookupPointer[] result = new LookupPointer[option[OPT_APP]];

		ptr += PTR;
		ptr += LONG;
		for(int i = 0; i < option[OPT_APP]; i++){
			result[i] = getPointer(ptr, PTR);
			ptr += PTR;
		}

		return result;
	}

	public int getNextOne(int ptr){
		ptr += PTR + LONG;
		if(option[OPT_TYPE] == 0) ptr += option[OPT_APP] * PTR;
		int strlen = getInt(ptr, STRLEN);
		if(bodySize - bodyVacancy <= ptr + STRLEN + strlen) return -1;
		return ptr + STRLEN + strlen;
	}

	public String readString(int ptr){
		ptr += PTR + LONG;
		if(option[OPT_TYPE] == 0) ptr += option[OPT_APP] * PTR;
		int strlen = getInt(ptr, STRLEN);
		ptr += STRLEN;

		return getString(ptr, strlen);
	}

	public int readLength(int ptr){
		ptr += PTR + LONG;
		if(option[OPT_TYPE] == 0) ptr += option[OPT_APP] * PTR;
		int strlen = getInt(ptr, STRLEN);
		int retval = LONG + STRLEN + strlen;
		if(option[OPT_TYPE] == 0) retval += option[OPT_APP] * PTR;

		return retval;
	}

	public long readID(int ptr){
		ptr += PTR;
		long value = getLong(ptr, LONG);
		return value;
	}

	public int writeAll(int ptr, ContentPackage pkg){
		setPointer(ptr, PTR, pkg.getNext());
//		System.out.println("[[DEBUG:NX]] Write " + (pkg.getNext() == null? "null": ptr) + " to " + ptr);
		ptr += PTR;
		setLong(ptr, LONG, pkg.getID());
//		System.out.println("[[DEBUG:ID]] Write " + pkg.getID() + " to " + ptr);
		ptr += LONG;
		if(option[OPT_TYPE] == 0){
			for(int i = 0; i < option[OPT_APP]; i++){
				setPointer(ptr, PTR, pkg.getAppendix(i));
//				System.out.println("[[DEBUG:AP]] Write "
//					+ (pkg.getAppendix(i) == null? "null": pkg.getAppendix(i)) + " to " + ptr);
				ptr += PTR;
			}
		}
		setInt(ptr, STRLEN, pkg.getContentLength());
//		System.out.println("[[DEBUG:SL]] Write " + pkg.getContentLength() + " to " + ptr);
		ptr += STRLEN;
		store(pkg.getContentraw(), ptr);
		option[OPT_LEN]++;

		return pkg.length();
	}

	public void writeAppendixPointer(int ptr, int index, LookupPointer toapp){
		if(index >= option[OPT_APP] && option[OPT_TYPE] != 0) return;
		ptr += PTR + LONG + (index * PTR);

		setPointer(ptr, PTR, toapp);
//		 System.out.println("[[DEBUG:A2]] Write " + (toapp == null? "null": toapp) + " to "
//		 + ptr);
	}

	public void modifyNextPointer(int ptr, LookupPointer next){
		setPointer(ptr, PTR, next);
//		 System.out.println("[[DEBUG:N2]] Write " + (next == null? "null": next) + " to " + ptr);
	}

	// With appending points
	public synchronized int getNewPointer(ContentPackage pkg){
		int subtractVal = pkg.length();
		if(option[OPT_TYPE] != 0) subtractVal -= PTR * option[OPT_APP];
		if(this.bodyVacancy < subtractVal) return -1;
//		System.out.println("[DEBUG]Value: " + subtractVal);
		this.bodyVacancy -= subtractVal;
		return this.bodySize - this.bodyVacancy - subtractVal;
	}

	public int getCount(){
		return option[OPT_LEN];
	}

	public int setReferenceCount(int difference){
		option[OPT_REF] += difference;
		return option[OPT_REF];
	}

	@Override
	public int getOptionSize(){
		return OPTSIZE;
	}

	@Override
	public byte[] getOptionByteStream(){
		ByteArrayOutputStream stream = new ByteArrayOutputStream();
		try{
			for(int i = 0; i < OPTSIZE; i++)
				stream.write(ByteConverter.intToByte(option[i], 4));
		}catch(IOException e){}
		return stream.toByteArray();
	}

	@Override
	public int getOption(int index){
		if(index >= OPTSIZE) return -1;
		return option[index];
	}

	@Override
	public void setOption(int index, int value){
		if(index >= OPTSIZE) return;
		option[index] = value;
	}

	@Override
	public ArrayType toArrayType(){
		MessagePackObject[] objectArray = new MessagePackObject[4];
		this.setHeader();
		objectArray[0] = IntegerType.create(getID());
		objectArray[1] = IntegerType.create(getCluster());
		objectArray[2] = IntegerType.create(getType());
		objectArray[3] = IntegerType.create(getOption(OPT_TYPE));
		objectArray[4] = RawType.create(readAllByteArray());

		return ArrayType.create(objectArray);
	}

	@Override
	public String getClassName(){
		return new String("Content");
	}
}
